package com.qingfeng.medical.biz.hosp.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qingfeng.medical.base.domain.entity.BaseEntity;
import com.qingfeng.medical.base.handler.GeneralAuthException;
import com.qingfeng.medical.base.utils.DateWeek;
import com.qingfeng.medical.biz.auth.domain.entity.SysUser;
import com.qingfeng.medical.biz.auth.service.SysUserService;
import com.qingfeng.medical.biz.hosp.dao.ScheduleMapper;
import com.qingfeng.medical.biz.hosp.domain.entity.HospDepartment;
import com.qingfeng.medical.biz.hosp.domain.entity.Schedule;
import com.qingfeng.medical.biz.hosp.domain.ro.ScheduleWeekDayRo;
import com.qingfeng.medical.biz.hosp.domain.vo.DepScheduleVo;
import com.qingfeng.medical.biz.hosp.domain.vo.HospDepartmentVo;
import com.qingfeng.medical.biz.hosp.domain.vo.ScheduleDepPatientVo;
import com.qingfeng.medical.biz.hosp.domain.vo.ScheduleVo;
import com.qingfeng.medical.biz.hosp.domain.vo.ScheduleWeekDayVo;
import com.qingfeng.medical.biz.hosp.service.HospDepartmentService;
import com.qingfeng.medical.biz.hosp.service.ScheduleService;
import com.qingfeng.medical.biz.information.domain.entity.DoctorInfo;
import com.qingfeng.medical.biz.information.domain.vo.DoctorVo;
import com.qingfeng.medical.biz.information.service.DoctorInfoService;
import com.qingfeng.medical.biz.information.service.HospitalInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 排班管理
 *
 * @author 清风学Java
 * @date 2023-04-14 14:47:17
 */
@Service
public class ScheduleServiceImpl extends ServiceImpl<ScheduleMapper, Schedule> implements ScheduleService {

    @Autowired
    private HospDepartmentService hospDepartmentService;
    @Autowired
    private DoctorInfoService doctorInfoService;
    @Autowired
    private SysUserService sysUserService;
    @Autowired
    private HospitalInfoService hospitalInfoService;

    /**
     * 科室门诊关系
     *
     * @return
     */
    @Override
    public List<ScheduleDepPatientVo> findDepPatient() {
        List<HospDepartmentVo> hospDepartmentVos = hospDepartmentService.hospDepartmentList(null, null);

        if (CollUtil.isEmpty(hospDepartmentVos)) {
            return Collections.emptyList();
        }
        AtomicLong atomicLong = new AtomicLong(1L);
        // 过滤出状态是启用的数据
        return hospDepartmentVos.stream()
                .filter(h -> h.getStatus() != 0)
                .map(h -> {
                    ScheduleDepPatientVo scheduleDepPatientVo = ScheduleDepPatientVo.builder()
                            .id(atomicLong.get())
                            .label(h.getDepName())
                            .children(
                                    h.getHospDepartmentRo()
                                            .stream()
                                            .filter(p -> p.getStatus() != 0)
                                            .map(p -> ScheduleDepPatientVo.builder()
                                                    .id(p.getId())
                                                    .label(p.getPatientName())
                                                    .children(null)
                                                    .build()
                                            )
                                            .collect(Collectors.toList())
                            )
                            .build();
                    atomicLong.addAndGet(1);
                    return scheduleDepPatientVo;
                })
                .collect(Collectors.toList());
    }

    @Override
    public List<ScheduleDepPatientVo> findDepPatientByDoctorId(Long userId) {
        // 查询医生所属的科室门诊信息
        DoctorInfo doctorInfo = doctorInfoService.getOne(
                new LambdaQueryWrapper<DoctorInfo>()
                        .eq(DoctorInfo::getUserId, userId)
        );

        if (CollUtil.isEmpty(doctorInfo.getDepartmentPatient())) {
            return Collections.emptyList();
        }

        // 过滤出科室门诊id
        List<Long> depIds = doctorInfo.getDepartmentPatient()
                .stream()
                .flatMap(Collection::stream)
                .filter(id -> id != 0)
                .collect(Collectors.toList());

        List<HospDepartment> hospDepartmentList = hospDepartmentService.list(new LambdaQueryWrapper<HospDepartment>()
                .in(BaseEntity::getId, depIds)
                .eq(HospDepartment::getStatus, 1)
        );

        // 按科室进行分组
        return hospDepartmentList.stream()
                .collect(Collectors.groupingBy(
                                HospDepartment::getDepName
                        )
                ).entrySet()
                .stream()
                .map(entry -> {
                    ScheduleDepPatientVo build = ScheduleDepPatientVo.builder()
                            .id(1L)
                            .label(entry.getKey())
                            .children(
                                    entry.getValue()
                                            .stream()
                                            .map(e -> ScheduleDepPatientVo.builder()
                                                    .id(e.getId())
                                                    .label(e.getPatientName())
                                                    .build())
                                            .collect(Collectors.toList())
                            )
                            .build();
                    return build;
                })
                .collect(Collectors.toList());
    }

    /**
     * 分页查询排班日期数据
     *
     * @param page
     * @param limit
     * @param depId
     * @return
     */
    @Override
    public ScheduleWeekDayVo scheduleWeekDay(Long page, Long limit, Long depId) {
        // 查询排班周期
        Integer cycle = hospitalInfoService.list().get(0).getBookingRule().getCycle();
        // 根据当前时间计算后面排班日期范围的排版日期
        List<Date> workDateList = new ArrayList<>();
        for (int i = 0; i <= cycle; i++) {
            workDateList.add(
                    Date.from(
                            LocalDate.now()
                                    .plusDays(i)
                                    .atStartOfDay()
                                    .atZone(ZoneId.systemDefault())
                                    .toInstant()
                    )
            );
        }

        // 根据分页条件计算需要查询的日期
        int pageSize = page.intValue();
        int limitSize = limit.intValue();
        List<Date> dateList = workDateList.subList((pageSize - 1) * limitSize,
                ((pageSize - 1) * limitSize + limitSize) > workDateList.size() ?
                        workDateList.size() : ((pageSize - 1) * limitSize + limitSize)
        );

        // 只查询有效的日期
        List<Schedule> scheduleList = baseMapper.selectList(
                new LambdaQueryWrapper<Schedule>()
                        .eq(Schedule::getDepId, depId)
                        .in(Schedule::getWorkDate, dateList)
        );

        Map<String, List<Schedule>> scheduleMap = scheduleList.stream()
                .collect(
                        Collectors.groupingBy(
                                s -> s.getWorkDate().toString()
                        )
                );


        List<ScheduleWeekDayRo> scheduleWeekDayRos = dateList.stream()
                .map(d -> {
                    List<Schedule> schedules = scheduleMap.get(d.toString());
                    // 删选出没有停止的数据
                    List<Schedule> collect = Collections.emptyList();
                    if (CollUtil.isNotEmpty(schedules)) {
                        collect = schedules.stream()
                                .filter(s -> s.getStatus() != 0)
                                .collect(Collectors.toList());
                    }

                    return ScheduleWeekDayRo.builder()
                            .workDate(d)
                            .weekday(DateWeek.dateToWeek(
                                            d.toInstant()
                                                    .atZone(ZoneId.systemDefault())
                                                    .toLocalDate()
                                    )
                            )
                            .signalSourceStr(
                                    CollUtil.isEmpty(schedules) ? "未安排号源" :
                                            CollUtil.isEmpty(collect) ?
                                                    "停诊" :
                                                    collect.stream()
                                                            .mapToInt(Schedule::getAvailableNum)
                                                            .sum()
                                                            + "/"
                                                            + collect.stream()
                                                            .mapToInt(Schedule::getSignalSource)
                                                            .sum()
                            )
                            .build();
                })
                .collect(Collectors.toList());


        return ScheduleWeekDayVo.builder()
                .total(Long.parseLong(workDateList.size() + ""))
                .page(page)
                .limit(Long.parseLong(dateList.size() + ""))
                .scheduleWeekDayRoList(scheduleWeekDayRos)
                .build();
    }

    /**
     * 排班管理列表
     *
     * @param weekDay
     * @return
     */
    @Override
    public List<ScheduleVo> findScheduleVo(Date weekDay, Long depId) {
        // 根据日期查询所有的排班信息
        List<Schedule> scheduleList = baseMapper.selectList(
                new LambdaQueryWrapper<Schedule>()
                        .eq(Schedule::getWorkDate, weekDay)
                        .eq(Schedule::getDepId, depId)
        );

        if (CollUtil.isEmpty(scheduleList)) {
            return Collections.emptyList();
        }

        List<Long> doctorIds = scheduleList.stream()
                .map(Schedule::getDoctorId)
                .collect(Collectors.toList());

        // 查询医师信息，封装Map
        Map<Long, DoctorInfo> doctorInfoMap = doctorInfoService.list(
                        new LambdaQueryWrapper<DoctorInfo>()
                                .in(DoctorInfo::getUserId, doctorIds)
                )
                .stream()
                .collect(Collectors.toMap(
                                DoctorInfo::getUserId,
                                Function.identity()
                        )
                );

        // 查询医生基本信息
        Map<Long, SysUser> sysUserMap = sysUserService.list(
                        new LambdaQueryWrapper<SysUser>()
                                .in(SysUser::getId, doctorIds)
                )
                .stream()
                .collect(Collectors.toMap(
                                BaseEntity::getId,
                                Function.identity()
                        )
                );

        return scheduleList.stream()
                .map(s -> ScheduleVo.builder()
                        .id(s.getId())
                        .doctorId(s.getDoctorId())
                        .doctorName(sysUserMap.get(s.getDoctorId()).getName())
                        .position(doctorInfoMap.get(s.getDoctorId()).getPosition())
                        .doctorIntro(doctorInfoMap.get(s.getDoctorId()).getIntroduction())
                        .signalSource(s.getSignalSource())
                        .reservationNum(s.getReservationNum())
                        .availableNum(s.getAvailableNum())
                        .status(s.getStatus())
                        .price(s.getPrice())
                        .build()
                )
                .collect(Collectors.toList());
    }

    /**
     * 查询科室门诊下的空闲医生
     *
     * @param weekDay
     * @param depId
     * @return
     */
    @Override
    @Transactional(rollbackFor = GeneralAuthException.class)
    public List<DoctorVo> findFreeDoctorList(Date weekDay, Long depId) {
        // 先查询这个科室门诊下面是否有医生
        List<DoctorInfo> doctorInfoList = doctorInfoService.list(
                new LambdaQueryWrapper<DoctorInfo>()
                        .like(DoctorInfo::getDepartmentPatient, "%" + depId + "%")
                        .like(DoctorInfo::getWeekday, "%" + DateWeek.dateToWeek(
                                        weekDay.toInstant()
                                                .atZone(ZoneId.systemDefault())
                                                .toLocalDate()
                                ) + "%"
                        )

        );

        if (CollUtil.isEmpty(doctorInfoList)) {
            return Collections.emptyList();
        }

        // 先根据日期和科室门诊信息查询已经排班的医生信息
        List<Schedule> scheduleList = baseMapper.selectList(
                new LambdaUpdateWrapper<Schedule>()
                        .eq(Schedule::getWorkDate, weekDay)
                        .eq(Schedule::getDepId, depId)
        );

        Set<Long> exitsDoctorIds = new HashSet<>();
        if (CollUtil.isNotEmpty(scheduleList)) {
            exitsDoctorIds = scheduleList.stream()
                    .map(Schedule::getDoctorId)
                    .collect(Collectors.toSet());
        }

        // 查询医生并排除已经排班的医生信息
        Set<Long> finalExitsDoctorIds = exitsDoctorIds;
        List<DoctorInfo> doctorInfos = doctorInfoList.stream()
                .filter(d -> !finalExitsDoctorIds.contains(d.getUserId()))
                .collect(Collectors.toList());

        if (CollUtil.isEmpty(doctorInfos)) {
            return Collections.emptyList();
        }

        // 查询用户基础信息
        List<SysUser> sysUserList = sysUserService.list(
                new LambdaQueryWrapper<SysUser>()
                        .in(SysUser::getId, doctorInfos.stream()
                                .map(DoctorInfo::getUserId)
                                .collect(Collectors.toList())
                        )
                        .eq(SysUser::getStatus, 1)
        );

        if (CollUtil.isEmpty(sysUserList)) {
            return Collections.emptyList();
        }

        Map<Long, DoctorInfo> doctorInfoMap = doctorInfos.stream()
                .collect(Collectors.toMap(
                                DoctorInfo::getUserId,
                                Function.identity()
                        )
                );

        return sysUserList.stream()
                .map(s -> DoctorVo.builder()
                        .id(s.getId())
                        .name(s.getName())
                        .position(doctorInfoMap.get(s.getId()).getPosition())
                        .weekday(doctorInfoMap.get(s.getId())
                                .getWeekday()
                                .stream()
                                .collect(Collectors.joining("、"))
                        )
                        .introduction(doctorInfoMap.get(s.getId()).getIntroduction())
                        .build())
                .collect(Collectors.toList());
    }

    /**
     * 人工进行一键排班
     *
     * @param userId
     * @param weekDay
     * @param depId
     */
    @Override
    public void saveSchedule(Long userId, Date weekDay, Long depId) {
        DoctorInfo doctorInfo = doctorInfoService.getOne(
                new LambdaQueryWrapper<DoctorInfo>()
                        .eq(DoctorInfo::getUserId, userId)
        );

        baseMapper.insert(Schedule.builder()
                .doctorId(userId)
                .depId(depId)
                .workDate(weekDay)
                .signalSource(100)
                .reservationNum(0)
                .availableNum(100)
                .status(1)
                .price(doctorInfo.getPosition().getPrice())
                .build()
        );
    }

    /**
     * 门诊停诊，那个门诊停诊，当天的排班全部修改状态为禁用，并发布停诊通知
     *
     * @param weekDay
     * @param depId
     */
    @Override
    @Deprecated
    public void ceaseSchedule(Date weekDay, Long depId) {

        // TODO 停诊公告
    }

    /**
     * 修改排班状态
     *
     * @param id
     */
    @Override
    public void updateStatusById(Long id) {
        Schedule schedule = baseMapper.selectById(id);
        schedule.setStatus(schedule.getStatus() == 1 ? 0 : 1);
        baseMapper.updateById(schedule);
    }

    /**
     * 根据Id查询排班挂号信息
     *
     * @param id
     * @return
     */
    @Override
    public DepScheduleVo depScheduleInfo(Long id) {
        //查询排班信息
        Schedule schedule = baseMapper.selectById(id);

        // 查询科室门诊信息
        HospDepartment hospDepartment = hospDepartmentService.getById(schedule.getDepId());

        // 查询医师信息
        DoctorInfo doctorInfo = doctorInfoService.getOne(
                new LambdaQueryWrapper<DoctorInfo>()
                        .eq(DoctorInfo::getUserId, schedule.getDoctorId())
        );

        // 查询用详情
        SysUser sysUser = sysUserService.getById(schedule.getDoctorId());

        return DepScheduleVo.builder()
                .scheduleId(schedule.getId())
                .workDate(schedule.getWorkDate())
                .depName(hospDepartment.getDepName())
                .patientName(hospDepartment.getPatientName())
                .doctorId(schedule.getDoctorId())
                .doctorName(sysUser.getName())
                .position(doctorInfo.getPosition().getMsg())
                .doctorIntro(doctorInfo.getIntroduction())
                .price(schedule.getPrice())
                .build();
    }

}